<!DOCTYPE html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT" />










<meta name="description" content="1. springboot与SSM的关系： SSM需要配置Tomcat打包是个war包，war包需要在一定的环境下才能够运行；  springboot是内置了Tomcat(内部集成，从主函数里面启动)打包后是jar包；springboot配置简化，没有SSM那么麻烦  为什么做后台app用Springboot比较方便，是因为springboot是jar包，在运行的时候不需要tomcat,只需要有j">
<meta property="og:type" content="article">
<meta property="og:title" content="PublicBike">
<meta property="og:url" content="http://smallkk.gitee.io/2018/02/28/RecoverMaMaBike/index.html">
<meta property="og:site_name" content="Hi , 小坤坤">
<meta property="og:description" content="1. springboot与SSM的关系： SSM需要配置Tomcat打包是个war包，war包需要在一定的环境下才能够运行；  springboot是内置了Tomcat(内部集成，从主函数里面启动)打包后是jar包；springboot配置简化，没有SSM那么麻烦  为什么做后台app用Springboot比较方便，是因为springboot是jar包，在运行的时候不需要tomcat,只需要有j">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2018-05-01T04:28:13.973Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="PublicBike">
<meta name="twitter:description" content="1. springboot与SSM的关系： SSM需要配置Tomcat打包是个war包，war包需要在一定的环境下才能够运行；  springboot是内置了Tomcat(内部集成，从主函数里面启动)打包后是jar包；springboot配置简化，没有SSM那么麻烦  为什么做后台app用Springboot比较方便，是因为springboot是jar包，在运行的时候不需要tomcat,只需要有j">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://smallkk.gitee.io/2018/02/28/RecoverMaMaBike/"/>





  <title>PublicBike | Hi , 小坤坤</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Hi , 小坤坤</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://smallkk.gitee.io/2018/02/28/RecoverMaMaBike/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="S-Name Small·kk">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Hi , 小坤坤">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">PublicBike</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-02-28T09:30:52+08:00">
                2018-02-28
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h6 id="1-springboot与SSM的关系："><a href="#1-springboot与SSM的关系：" class="headerlink" title="1. springboot与SSM的关系："></a>1. springboot与SSM的关系：</h6><ol>
<li><p>SSM需要配置Tomcat打包是个war包，war包需要在一定的环境下才能够运行；</p>
</li>
<li><p>springboot是内置了Tomcat(内部集成，从主函数里面启动)打包后是jar包；springboot配置简化，没有SSM那么麻烦</p>
</li>
<li><p>为什么做后台app用Springboot比较方便，是因为springboot是jar包，在运行的时候不需要tomcat,只需要有java环境就够了</p>
</li>
<li><p>微服务，分布式，主流的实现方式是用Springboot</p>
</li>
<li>如果是管理系统等需要用页面来展示的话，那么springboot对JSP，velocity的支持不是很好</li>
<li>RestController 是 spring4.x才有的，整个Controller下都是JSON，不需要像之前那样每个方法都要有@Res..body</li>
<li>SpringBoot里面开始事务：在主函数里面加上注解：@EnableTransactionManagement</li>
</ol>
<h6 id="2-Others"><a href="#2-Others" class="headerlink" title="2. Others:"></a>2. Others:</h6><p>==注意点==：<br>平常Java开发里面的“数据源”是直接写在Java代码中或者配置文件中的开发人员可以知道但是不利于隐私；而“JNDI”的数据源是写在Tomcat里面的，是封装好了的，此时开发人员不会知道数据源怎么配置的和配置在哪里的</p>
<p>JNDI (Java Naming and Directory Interface,Java命名和目录接口)</p>
<p>在Springboot里面，开发账号都是低权限的账号而且是只读状态，允许调试不允许修改；发布账号(高级账号)一般都在运维人员或者组长项目经理那里才知道</p>
<p>配置文件：perproties写起来太麻烦，所以用yml有层级公用感觉,如果有情况需要用到的有序配置的话需要.ini</p>
<h5 id="3-测试"><a href="#3-测试" class="headerlink" title="3. 测试"></a>3. 测试</h5><p>SpringBoot测试类的配置：</p>
<pre><code>@RunWith(SpringRunner.class)
@SpringBootTest(classes = RecovermamabikeApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//第一个参数：运行类是哪个类，也就是入口从哪儿开始；
//后面的两个参数是-这是一个web项目，需要springmvc的支持，即webEnvironment;
//后面的RANDOM_PORT表示下面的那个端口随机
</code></pre><p>在测试类里面需要添加的两个东西：</p>
<pre><code>@Autowired
private TestRestTemplate restTemplate;
//主要是模拟请求：测试controller层要返回JSON数据的话就要这个东西
@LocalServerPort
private int port;
//在web容器中需要监听一个端口，模拟一个请求过去看看测试通过不通过
//如果是普通测试，就如上一个类配置中有个“.RANDOM_PORT”
//如果是要跑服务器的话就需要指定一个特定的端口
</code></pre><p>写测试方法：</p>
<pre><code> @Test
public void contextLoads() {
    String result = restTemplate.getForObject(&quot;/user/hello&quot;, String.class);
    //controller层下面这个/user/hello/路径对应的方法返回的是一个String,所以此处的返回类型就是String；
    //用restTemplate去模拟一个请求得到那个方法返回；
    System.out.println(result);
    //在控制台输出测试内容的结果
}
</code></pre><h5 id="4-springBoot中数据处理："><a href="#4-springBoot中数据处理：" class="headerlink" title="4. springBoot中数据处理："></a>4. springBoot中数据处理：</h5><p>在springBoot中关于JSON传值类型的处理：<br>因为在SpringBoot里面是内置了”jackson”,但是jackson并没有fastjson那么爽；<br>在SSM配置中配置fastjson是在spring-mvc.xml里面配置了一个消息转换器：MessageConvert</p>
<pre><code>fastjson与jackson的区别：
1. jackson会将数据库里字段为“null”的也转换了特别影响数据的转换而且为null的也没有用；
2.  fastjson就改进了这个，不会将为空的字段也转换！
</code></pre><p>如何将jackson转换成fastjson：</p>
<p>   需要声明一个@Bean去覆盖，声明到任何地方都可以，但是像springboot这种框架集成的，一般都是声明在主函数之中的：</p>
<pre><code>/**
  * Author song  醉美柳舞之众星捧月
  * Date &amp; Time  2018/1/15 13:22
  * Description  fastjson 覆盖 jackjson 的消息转换器  =====&gt;&gt;&gt;  数据库中的字段为null就不会转换过来
  */
 @Bean
 public HttpMessageConverters fastJsonHttpMessageConverters() {
     FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
     HttpMessageConverter&lt;?&gt; converter = fastConverter;
     return new HttpMessageConverters(converter);
 }
 //将这个@Bean放在springboot的主函数中
 //方法名称可以随便写，其他的都是固定的
 //声明一个@Bean是让spring能够扫描到
 //返回的时候，返回的这个新的东西会将原来返回的东西
</code></pre><h6 id="5-日志整合"><a href="#5-日志整合" class="headerlink" title="5. 日志整合"></a>5. 日志整合</h6><p>log4j : 将所有类别的信息都可以输出在文本(混乱，不能分类)</p>
<p>logback：能够清晰的分别输出不同类型的日志在不同的文件中</p>
<pre><code>     application.yml配置：
      logging:
  config: classpath:logback.xml


    // logback的两个依赖:(同类东西 版本号必须要一致 )
     &lt;dependency&gt;
        &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
        &lt;artifactId&gt;logback-core&lt;/artifactId&gt;
        &lt;version&gt;1.2.3&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
        &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
        &lt;version&gt;1.2.3&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
     &lt;dependency&gt;
        &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
        &lt;artifactId&gt;logback-access&lt;/artifactId&gt;
        &lt;version&gt;1.1.3&lt;/version&gt;
    &lt;/dependency&gt;
ps: logback的日志需要人为写(主动记录)，才会写进错误日志里：
    人为写的动作为： log.error(&quot;ErroeMessage:&quot;,e);
</code></pre><p>springBoot中可能，只是可能不会读取“resource”文件夹里面的xml配置文件等，那么需要：</p>
<pre><code>&lt;resource&gt;
    &lt;directory&gt;${basedir}/src/main/resources&lt;/directory&gt;
    &lt;includes&gt;
        &lt;!--&lt;include&gt;contexts/&lt;/include&gt;--&gt;
        &lt;!--&lt;include&gt;*.xml&lt;/include&gt;--&gt;
        &lt;include&gt;*.yml&lt;/include&gt;
        &lt;include&gt;*.properties&lt;/include&gt;
        &lt;include&gt;*.xml&lt;/include&gt;
        &lt;include&gt;enc_pri&lt;/include&gt;
    &lt;/includes&gt;
&lt;/resource&gt;
</code></pre><p>注入异常之一：</p>
<pre><code>@Autowared
privite UserService userService;
//有的时候注入启动查看debug可以看到，并不是自己想的这个userService
//并且还找不到userService里面的方法，那么此时：

  @Autowared
  @Qualifiter(&quot;***.Impl&quot;)
 privite UserService userService;
//  @Qualifiter 表示这个Mapper注入哪一个实现类才合格
</code></pre><p>==用户登录==：</p>
<h5 id="6-session一致性问题："><a href="#6-session一致性问题：" class="headerlink" title="6. session一致性问题："></a>6. session一致性问题：</h5><p>没有浏览器，是安卓端，没有cookie概念那么session那么存活？(session是基于cookie的)</p>
<pre><code>基于session这样的东西，是有状态的，有标志性的,
是一个有状态的请求的statefull;
然而安卓app端的session是无状态的
</code></pre><p>因此采用session外置：Nginx轮询到不同的服务器上都可以访问到这个外置的session</p>
<p>session不能局限在Tomcat，只要能够标识一个会话的就可以当做是一个session,唯一识别用户身份的什么东西码都可以叫做session</p>
<p>用户登录的时候生成一个token(自定义的，相当于session)，将这个session存进redis，设置超时，用户每次请求 都要带着token 服务端用来做校验</p>
<h5 id="7-About-JSON"><a href="#7-About-JSON" class="headerlink" title="7. About JSON"></a>7. About JSON</h5><p>这个方法接收的参数是JSON</p>
<pre><code>public ApiResult&lt;String&gt; login(@RequestBody LoginInfo loginInfo) {
</code></pre><p>将JSON转化为字符串：</p>
<pre><code>JSONObject jsonObject = JSON.parseObject(decryptData);
            String mobile = jsonObject.getString(&quot;mobile&quot;);
            String verifycode = jsonObject.getString(&quot;code&quot;);
</code></pre><h5 id="8-从文件配置中取值"><a href="#8-从文件配置中取值" class="headerlink" title="8. 从文件配置中取值"></a>8. 从文件配置中取值</h5><p>①:在resource资源文件中的配置值：</p>
<pre><code>    redis:
host: localhost
port: 6379
auth:
max-idle: 5
max-total: 10
max-wait-millis: 3000
</code></pre><p>②:声明到代码并@Component让spring扫描到</p>
<pre><code>@Component
public class Parameters {
    /** redis config start */
    @Value(&quot;${redis.host}&quot;)
    private String redisHost;
    @Value(&quot;${redis.port}&quot;)
    private int redisPort;
    @Value(&quot;${redis.max-total}&quot;)
    private int redisMaxTotal;
    @Value(&quot;${redis.max-idle}&quot;)
    private int redisMaxIdle;
    @Value(&quot;${redis.max-wait-millis}&quot;)
    private int redisMaxWaitMillis;
    /** redis config end */
</code></pre><p>③:在需要值的地方取值</p>
<pre><code>JedisPoolConfig config = new JedisPoolConfig();
       config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
       config.setMaxIdle(parameters.getRedisMaxIdle());
       config.setMaxTotal(parameters.getRedisMaxTotal());
       jedisPool = new JedisPool(config, parameters.getRedisHost(), parameters.getRedisPort(), 3000);
</code></pre><p>在本项目中的==AES与RSA==加密流程:</p>
<p>RSA生成密钥对(私钥，公钥)</p>
<p>AES加密： 明文 + AES的公钥 = 密文</p>
<p>RSA加密： 用 RSA的公钥去加密AES的公钥</p>
<p>RSA解密： 用 RSA的私钥去解密上面加密的”AES的公钥”的结果</p>
<p>AES解密： 用RSA解密的结果 + 密文 = 明文</p>
<p>RSA 基于 大因式分解 </p>
<p>HTTP 不安全 传输过程 完全暴露</p>
<ul>
<li style="list-style: none"><input type="checkbox" checked> 单车加密：AES对称加密明文：  明文 + 密钥 = 密文；      安卓端的密钥是根据用户名等信息提供生成(随机)</li>
</ul>
<p>传输过程：用RSA非对称加密去加密AES对称的密钥 ，jiamiMethod(AES的密钥)； (RSA对称加密的公钥和私钥看看能不能根据用户的信息也随机生成)</p>
<p>然后安卓端的代码，再代码混淆，不会将存在安卓端本地的AES公钥给反编译出来</p>
<h6 id="9-代码一定执行"><a href="#9-代码一定执行" class="headerlink" title="9. 代码一定执行"></a>9. 代码一定执行</h6><p>代码中某一个方法，并不会被调用但是希望这个方法能够执行：</p>
<p>引入注解：@PostConstruct;作用效果与静态代码块的功能一致：</p>
<pre><code>   /**
     * 初始化连接池
     */
    @PostConstruct
    public void init() throws MaMaBikeException {
        try {
// 这个方法就算不被调用，但是一定会执行！ 
</code></pre><p>返回主键:</p>
<pre><code>useGeneratedKeys=&quot;true&quot;
keyProperty=&quot;id&quot;
</code></pre><p>判断是否为空:</p>
<pre><code>①: StringUtils.isBlank() 
//comons.lang3 元素
②: CollectionUtils.isEmpty()   
//CollectionUtils 集合
</code></pre><p>文件占位符解析</p>
<pre><code>/**
 * 用于properties文件占位符解析
 * @return
 * @Value(&quot;#{&apos;${security.noneSecurityPath}&apos;.split(&apos;,&apos;)}&quot;) 作用是识别上面的&quot;#&quot;&amp;&quot;split&quot;
 */
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
    return new PropertySourcesPlaceholderConfigurer();
}
</code></pre><h5 id="10-SpringSecurity"><a href="#10-SpringSecurity" class="headerlink" title="==10==. SpringSecurity"></a>==10==. SpringSecurity</h5><hr>
<p>授权 与 预授权</p>
<p>授权: 在本系统内部加载权限信息</p>
<p>预授权: 在别的系统 或者别的代码中 已经进行过授权</p>
<p>pre manager —- provider (一个或N个 提供授权信息) entrypoint 统一异常处理</p>
<p>SpringSecurity类配置：</p>
<pre><code>@Configurable
@EnableWebSecurity
@EnableGlobalMethodSecurity
//@Configurable是能够让spring扫描到
//@EnableWebSecurity是开启SpringSecurity在web中的应用
//@EnableGlobalMethodSecurity在方法上面打注解进行权限拦截
public class SecurityConfig {
}
</code></pre><p>csrf 是一种 跨站脚本攻击 相当于伪造表单 类似钓鱼网站这种 复制网站表单欺骗用户 (将网站设置成唯一性验证就好,别人就复制不到了,要用到html里面的隐藏域由后端来生成数据)</p>
<p>将集合转为String数组：</p>
<pre><code>parameters.getNoneSecurityPath().toArray(new String[parameters.getNoneSecurityPath().size()]
//下面是将数组转成集合
Arrays.asList(authorities)
</code></pre><p>Spring的路径匹配器：</p>
<pre><code>private AntPathMatcher matcher = new AntPathMatcher();
</code></pre><p>本次流程：</p>
<ul>
<li style="list-style: none"><input type="checkbox" checked> ①：发送请求后先到<code>RestPreAuthenticatedProcessingFilter</code>类里的<code>getPreAuthenticatedPrincipal</code>方法获取用户信息(检查是否需要授权,如果是登录则给任意权限进入,如果是用车或者修改信息则给单车用户权限,与此同时校验传入的版本号和Token并返回session错误信息)</li>
<li style="list-style: none"><input type="checkbox" checked> ②：进入到<code>RestAuthenticationProvider</code>类里面的<code>supports</code>方法判断从上面(RestPreAuthenticatedProcessingFilter类)传过来的Object是不是想要的东西,如果是则返回true</li>
<li style="list-style: none"><input type="checkbox" checked> ③：进入到<code>RestAuthenticationProvider</code>类里面的<code>authenticate</code>方法进行校验从上面(RestPreAuthenticatedProcessingFilter类)传递过来的授权的角色判断此处应该返回什么信息,如果有这个权限的角色则返回验证通过反之,抛出异常</li>
<li style="list-style: none"><input type="checkbox" checked> ④：如果上面(③)返回的是通过的话,此时应该进入路径请求的Controller层方法进行业务逻辑代码</li>
<li style="list-style: none"><input type="checkbox" checked> ⑤：若整个流程出现问题或者上面(③)抛出异常的话,此时会进入到<code>RestAuthenticationEntryPoint</code>类里面的<code>commence</code>的方法进行统一异常处理;根据前面(①)提到的session错误进行相应的返回并且设置跨域请求！</li>
<li style="list-style: none"><input type="checkbox" checked> ###### SpringSecurity End</li>
</ul>
<p>跨域设置：</p>
<pre><code>try {
           //设置跨域请求 请求结果json刷到响应里 前后端分离的时候都要这么设置
           // 这种设置是  后台端 与 前端或者安卓端 不在一块内存中 或者不在同一台服务器中
           httpServletResponse.setHeader(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;);
           httpServletResponse.setHeader(&quot;Access-Control-Allow-Methods&quot;, &quot;POST,GET,OPTIONS,DELETE&quot;);
           httpServletResponse.setHeader(&quot;Access-Control-Max-Age&quot;, &quot;3600&quot;);
           httpServletResponse.setHeader(&quot;Access-Control-Allow-Headers&quot;, &quot;Z-Requested-With,
           user-token,Content-Type,Accept,version,type,platform&quot;);
           httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
           // 这上面的 5行  都是强制代码 ;固然要这么做
           httpServletResponse.getWriter().write(JSON.toJSONString(result));
           //上面这一行 是将上面的result写入即返回
           httpServletResponse.flushBuffer();
       } catch (Exception er) {
           log.error(&quot;Fail to send 401 httpServletResponse&quot;, er.getMessage());
       }
</code></pre><h5 id="11-短信"><a href="#11-短信" class="headerlink" title="11. 短信"></a>11. 短信</h5><ul>
<li><p>[x] 短信是http接口，需要http请求;平常用到的是postman来做http请求;在代码中 Http client 这个jar包来做辅助,模拟POST/GET请求</p>
</li>
<li><p>获取IP地址(客户端真正的ip地址,不会随Nginx转发而改变的原始ip)</p>
<pre><code>protected String getIpFromRequest(HttpServletRequest request) {
    String ip = request.getHeader(&quot;x-forwarded-for&quot;);
    if (ip == null || ip.length() == 0 || &quot;unknown&quot;.equalsIgnoreCase(ip)) {
        ip = request.getHeader(&quot;Proxy-Client-IP&quot;);
    }
    if (ip == null || ip.length() == 0 || &quot;unknown&quot;.equalsIgnoreCase(ip)) {
        ip = request.getHeader(&quot;WL-Proxy-Client-IP&quot;);
    }
    if (ip == null || ip.length() == 0 || &quot;unknown&quot;.equalsIgnoreCase(ip)) {
        ip = request.getRemoteAddr()
    }
    return ip.equalsIgnoreCase(&quot;0:0:0:0:0:0:1&quot;) ? &quot;127.0.0.1&quot; : ip;
}
</code></pre></li>
<li><p>[ ] 模拟POST请求：</p>
</li>
</ul>
<pre><code>public static String post(String url, Map&lt;String, String&gt; paramsMap) {
        // 创造一个HttpClients的客户端
        CloseableHttpClient client = HttpClients.createDefault();
        String responseText = &quot;&quot;;
        CloseableHttpResponse response = null;
        try {
            HttpPost method = new HttpPost(url);
            if (paramsMap != null) {
                List&lt;NameValuePair&gt; paramList = new ArrayList&lt;NameValuePair&gt;();
                for (Map.Entry&lt;String, String&gt; param : paramsMap.entrySet()) {
                    NameValuePair pair = new BasicNameValuePair(param.getKey(), param.getValue());
                    paramList.add(pair);
                }
                method.setEntity(new UrlEncodedFormEntity(paramList, ENCODING));
            }
            response = client.execute(method);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                responseText = EntityUtils.toString(entity);
            }
        } catch (Exception e) {
            log.error(&quot;http request failed&quot;, e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {
                log.error(&quot;&quot;, e);
            }
        }
        return responseText;
    }
</code></pre><p>短信发送类:</p>
<pre><code>@Component(&quot;smsProcessor&quot;)
public class SmsProcessor {
    /**
     * SmsProcessor
     * 既可以发送短信  又可以监听发送短信的队列去消费短信 去调用
     */
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;
    //调用ActiveMQ的消息发送器
    @Autowired
    @Qualifier(&quot;verCodeService&quot;)
    private SmsSender sendSms;
    /**
     * 消息发送的方法
     */
    public void sendSmsToQueue(Destination destination, final String message) {
        //destination 表示短信发送的 名字在哪儿/目的地在哪儿
        jmsMessagingTemplate.convertAndSend(destination, message);
        // message 一般都是转成json传出去
    }
    //destination = &quot;sms.queue&quot; 表示目的是&quot;sms.queue&quot; 短信队列
    // 表示这个要被监听
    @JmsListener(destination = &quot;sms.queue&quot;)
    public void doSendSmsMessage(String text) {
        //text是收到的消息
        JSONObject jsonObject = JSON.parseObject(text);
        sendSms.sendSms(jsonObject.getString(&quot;mobile&quot;), jsonObject.getString(&quot;tplid&quot;), jsonObject.getString(&quot;vercode&quot;));
    }
}
</code></pre><h5 id="12-自行车"><a href="#12-自行车" class="headerlink" title="12. 自行车"></a>12. 自行车</h5><hr>
<p>查找附件单车  GPS美国的 全球定位系统 N多卫星 以及备用卫星 全球坐标系经纬度<br>             中国的北斗  欧洲的伽利略  俄罗斯的格洛纳斯<br>用户登录APP 上报经纬度 服务器查找附件的单车 返回单车的经纬度 安卓对接百度地图 把经纬度坐标点显示在地图上  图标可以显示</p>
<p>开锁： 改变单着的状态 让其他用户搜索不到这辆车  操作：设计推送：就是服务器和客户端沟通<br>骑行历史记录 用户一边骑车 车子要 不间断的上报坐标 服务器记录</p>
<p>服务器查找附件的单车 : 上传的坐标</p>
<p>geoHash (地理哈希值)计算地理; 支持geoHash的是NOSQL ， NOSQL对事务的支持很弱<br>mysql RDS 关系型数据库 强项 是 事务 ACID ：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）。</p>
<p>MongoDB ElasticSearch 全文检索的中间件 可以理解为数据库</p>
<p>MongoDB是存文档的数据库 BSON JSON</p>
<table>
<thead>
<tr>
<th>SQL术语</th>
<th>Mongodb术语</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据库： database</td>
<td>数据库： database</td>
</tr>
<tr>
<td>数据库表: table</td>
<td>集合: collection</td>
</tr>
<tr>
<td>数据记录行： row</td>
<td>文档： document</td>
</tr>
<tr>
<td>数据字段： column</td>
<td>域：field</td>
</tr>
<tr>
<td>表链接：table joins</td>
<td>Mg不支持表链接</td>
</tr>
<tr>
<td>主键：primary key</td>
<td>Mg自动将_id设为主键：primary key</td>
</tr>
</tbody>
</table>
<p>启动命令: </p>
<pre><code>在 G:\Plugins\MongoDB\Server\3.4\bin 下cmd ：
mongod --dbpath G:\Plugins\MongoDB\mongoData
</code></pre><p>搜索：</p>
<pre><code>db.getCollection(&apos;bike-position&apos;).find({})
//括号中跟要搜索的东西,为空即为全部
</code></pre><p>ORM: spring data 是 ORM 即 从Java到数据库的实体映射</p>
<h5 id="13-骑行-解锁"><a href="#13-骑行-解锁" class="headerlink" title="13. 骑行 解锁"></a>13. 骑行 解锁</h5><ul>
<li style="list-style: none"><input type="checkbox" checked> RSA解密出现问题;Java端与安卓端加密方式有些不同即,在解密过后也会出现点问题(Java代码生成的公钥在Java代码下可以用但是在 安卓端会解析失败(可能是Java生成的公钥有分段空格));解决:Openssl加密方式生成的公钥是没有空格的,Java端和安卓端都是可以使用的(openssl是用命令生成的),但是通过这样的公钥后在安卓端经过加密的东西到了JAVA端解密又会失败(会报错,抛出异常 algid parse error , not a sequence..,百度报错代码进入stackOverflow网站给出的解决方案是要贴入一个第三方包),那么此时Java端就要引入一个新的东西,”第三方解密包”</li>
</ul>
<p>推送原理 socket长连接 Netty是NIO网络通信的框架搞长连接这块非常牛</p>
<p>两种第三方: 极光推送 百度云推送</p>
<p>当前共5张表;<br>bike、ride_fee、ride_record、user、wallet;此处还应该有微信一张表和支付宝一张表,再来分库分表</p>
<ul>
<li><p>涉及到金额的一般用Decimal or BigDecimal;</p>
<ul>
<li>钱之间的比较就是:<br>wallet.getRemainSun().compareTO(New BigDecimal(1)&lt;0)<ul>
<li>cost = fee.getFee().multiply(new BigDecimal((intMin / minUnit) + 1));</li>
</ul>
</li>
<li>//BigDecimal 算乘法 用 multiply</li>
</ul>
</li>
<li><p>[x] 在单车骑行代码实现中:涉及到了事务回滚机制<code>@Transactional(rollbackFor = Exception.class)</code>但是这个涉及到的数据源有Mysql和Mongodb两种数据库,要么同时成功要么同时失败;这样是非常困难的(经常就会出现 车已经锁了,但是计费并没有结束 此时就需要连续客服 Mongodb没有事务这个概念的)</p>
</li>
</ul>
<ul>
<li style="list-style: none"><input type="checkbox" checked> 单车还有页面后台端,管理界面</li>
<li style="list-style: none"><input type="checkbox" checked> 迭代开发的时候还没有开发app里面的排碳量什么的</li>
<li style="list-style: none"><input type="checkbox" checked> // 此处不能用 new Date().toString 不是因为过时,而是因为这个时间与Mongodb中的时间不相匹配,导致安卓端调用数据会异常</li>
<li><pre><code>rideRecord.setRecordNo(System.currentTimeMillis() + RandomNumberCoder.randomNo());
</code></pre></li>
<li><p>[x] 订单号:</p>
<pre><code>rideRecord.setRecordNo(System.currentTimeMillis() 
+ RandomNumberCoder.randomNo());
//这个并不严谨;当前只是在一定的范围类可控不重复就行;
//但是在超级大并发的情况下就得重新考虑
</code></pre></li>
<li><p>MongoDB在java代码中的SQL:</p>
<pre><code> Query query = Query.query(Criteria.where(&quot;bike_no&quot;).is(bikeLocation.getBikeNumber()));
 //查询
① Update update = Update.update(&quot;status&quot;, BIKE_LOCK).set(&quot;location.coordinates&quot;, bikeLocation.getCoordinates());
//push类似于堆栈那种   因为MongoDB里面的代码是BSON数组,一个坐标点一个数组经纬度坐标,push就是往最后一个数组后面叠加一个数组
② Update update = new Update().push(&quot;contrail&quot;, new BasicDBObject(&quot;loc&quot;, bikeLocation.getCoordinates()));
 //更新
 mongoTemplate.updateFirst(query, update, &quot;bike-position&quot;);
 //执行
</code></pre></li>
<li>解锁或者锁车规则：<ul>
<li>必须要有用户的Token验证和每个单车独一无二的Token验证;验证通过了才可以进入单车解锁或者锁车的接口</li>
</ul>
</li>
</ul>
<h5 id="14-骑行轨迹"><a href="#14-骑行轨迹" class="headerlink" title="14. 骑行轨迹"></a>14. 骑行轨迹</h5><p>卡尔曼滤波算法 百度地图补点</p>
<p>BasicDBObject 这个东西像BSON而且类似Map,有Key与Value之分</p>
<pre><code>BasicDBObject obj = new BasicDBObject(&quot;key&quot;,value);
</code></pre><h5 id="15-Swagger"><a href="#15-Swagger" class="headerlink" title="15. Swagger"></a>15. Swagger</h5><ol>
<li><p>jar</p>
<pre><code>&lt;dependency&gt;
        &lt;groupId&gt;io.springfox&lt;/groupId&gt;
        &lt;artifactId&gt;springfox-swagger2&lt;/artifactId&gt;
        &lt;version&gt;2.6.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.springfox&lt;/groupId&gt;
        &lt;artifactId&gt;springfox-swagger-ui&lt;/artifactId&gt;
        &lt;version&gt;2.6.1&lt;/version&gt;
    &lt;/dependency&gt;
</code></pre></li>
<li>与Fastjson冲突解决:(如果会有冲突的话)<ul>
<li>新建消息转换</li>
</ul>
</li>
</ol>
<pre><code>public class FastJsonHttpMessageConverterEx extends FastJsonHttpMessageConverter {
    public FastJsonHttpMessageConverterEx() {
        super();
        this.getFastJsonConfig().getSerializeConfig().put(Json.class, SwaggerJsonSerializer.instance);
    }
}
</code></pre><ul>
<li><p>覆盖(在启动类中)</p>
<pre><code>@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
    HttpMessageConverter&lt;?&gt; converter = fastConverter;
    return new HttpMessageConverters(converter);
}
</code></pre></li>
</ul>
<ol start="3">
<li>swagger 序列问题：</li>
</ol>
<pre><code>public class SwaggerJsonSerializer implements ObjectSerializer, ObjectDeserializer {

public final static SwaggerJsonSerializer instance = new SwaggerJsonSerializer();

@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
    SerializeWriter out = serializer.getWriter();
    Json json = (Json) object;
    out.write(json.value());
}

@Override
public &lt;T&gt; T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
    return null;
}

@Override
public int getFastMatchToken() {
    return 0;
}

}
//上面 2 中的SwaggerJsonSerializer.instance就是引用的此处的;
</code></pre><ol start="4">
<li>Swagger配置类</li>
</ol>
<ul>
<li style="list-style: none"><input type="checkbox" checked> 注意<code> .apis(RequestHandlerSelectors.any())</code>一定是<code>.any</code>不能是根当前的包名</li>
<li style="list-style: none"><input type="checkbox" checked> 注意<code>.paths(Predicates.not(PathSelectors.regex(“/error.*”)))</code>是必须要有的</li>
</ul>
<pre><code>       @Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())
               .paths(Predicates.not(PathSelectors.regex(&quot;/error.*&quot;)))
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(&quot;共享单车联调接口(复习)&quot;)
                .contact(new Contact(&quot;宋贤坤&quot;, &quot;www.smallkk.com&quot;, &quot;songxiankun2008@126.com&quot;))
                .version(&quot;版本信息 Version-2.0&quot;)
                .license(&quot;版权信息 License&quot;)
                .licenseUrl(&quot;www.smallkk.com&quot;)
                .build();
    }
}
</code></pre><ol start="5">
<li><p>Controller层的注解:eg</p>
<pre><code>@ApiOperation(value = &quot;解锁单车&quot;, notes = &quot;根据单车编号解锁单车&quot;, httpMethod = &quot;POST&quot;)
   @ApiImplicitParam(name = &quot;bike&quot;, value = &quot;单车编号&quot;, required = true, dataType = &quot;Bike&quot;)
   @RequestMapping(&quot;/unLockBike&quot;)
   public ApiResult unLockBike(@RequestBody Bike bike) throws MaMaBikeException {
</code></pre></li>
</ol>
<h5 id="16-终章"><a href="#16-终章" class="headerlink" title="16. 终章"></a>16. 终章</h5><ol>
<li>在单车位置信息中添加单车的具体状态（在地图展示的时候需要单车信息）</li>
<li>添加用户端接口查询单车状态（在开锁时需要轮训单车是否已经打开，因为开锁还涉及到单车端，应该是单车端真正解锁之后才算是解锁成功</li>
<li>添加获取用户信息接口（缺失）</li>
<li>添加用户钱包信息查询接口（缺失）</li>
<li>更改上报坐标接口，在上报坐标过程中如果已经锁车，则告诉客户端骑行结束（单车端锁车，客户端显示）</li>
<li>添加单车端接口获取单车状态（单车端使用不需要登录）</li>
<li>添加单车端接口锁定单车（单车端所用 不需要登录）</li>
<li>更改锁定单车接口，锁定单车是的位置信息将会更新到骑行记录中（锁定单车时上报的坐标是骑行记录中的最终坐标不能缺失）</li>
<li>MongoDB数据库中的时间与sqlList中时间戳不匹配导致找不到骑行单(Bug)</li>
</ol>
<h6 id="xx共享单车："><a href="#xx共享单车：" class="headerlink" title="xx共享单车："></a>xx共享单车：</h6><ul>
<li style="list-style: none"><input type="checkbox" checked> 此项目为时下最流行的共享单车实现，实现了周围单车定位，骑行轨迹等等功能。</li>
<li><p>[x] 技术上主要采用springboot、SSM、Mysql、springSecurity做安全校验、ActiveMQ消息队列、redis管理session、Nginx负载均衡、对接百度地图、对接短信、图片云存储。采用MongoDB做地理围栏服务，查找附近单车。</p>
</li>
<li><p>[ ] ActiveMQ/redis/Nginx问答在各自视频里面；</p>
</li>
<li style="list-style: none"><input type="checkbox"> ==GeoHash== 将全球地理分为36块，分的越细 精度越高，对每一个区域都划分了不同的经纬度;Base32算法计算距离的远近;MongoDB支持GeoHash所以就可以查询哪个区域之间的距离</li>
</ul>
<p>项目周期半年；2个后台，1个产品经理，1个美工，1个安卓，1个IOS，1个测试，1个运维 (敏捷开发 创业公司)</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2018/02/01/CAP原理和BASE思想/" rel="next" title="CAP原理和BASE思想">
                <i class="fa fa-chevron-left"></i> CAP原理和BASE思想
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2018/03/08/work-attendance/" rel="prev" title="work-attendance">
                work-attendance <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">S-Name Small·kk</p>
              <p class="site-description motion-element" itemprop="description">开源 | Java | 个人笔记 | 我的博客</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">12</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            

            

          </nav>

          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-6"><a class="nav-link" href="#1-springboot与SSM的关系："><span class="nav-number">1.</span> <span class="nav-text">1. springboot与SSM的关系：</span></a></li><li class="nav-item nav-level-6"><a class="nav-link" href="#2-Others"><span class="nav-number">2.</span> <span class="nav-text">2. Others:</span></a></li></ol></li><li class="nav-item nav-level-5"><a class="nav-link" href="#3-测试"><span class="nav-number"></span> <span class="nav-text">3. 测试</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#4-springBoot中数据处理："><span class="nav-number"></span> <span class="nav-text">4. springBoot中数据处理：</span></a><ol class="nav-child"><li class="nav-item nav-level-6"><a class="nav-link" href="#5-日志整合"><span class="nav-number">1.</span> <span class="nav-text">5. 日志整合</span></a></li></ol></li><li class="nav-item nav-level-5"><a class="nav-link" href="#6-session一致性问题："><span class="nav-number"></span> <span class="nav-text">6. session一致性问题：</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#7-About-JSON"><span class="nav-number"></span> <span class="nav-text">7. About JSON</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#8-从文件配置中取值"><span class="nav-number"></span> <span class="nav-text">8. 从文件配置中取值</span></a><ol class="nav-child"><li class="nav-item nav-level-6"><a class="nav-link" href="#9-代码一定执行"><span class="nav-number">1.</span> <span class="nav-text">9. 代码一定执行</span></a></li></ol></li><li class="nav-item nav-level-5"><a class="nav-link" href="#10-SpringSecurity"><span class="nav-number"></span> <span class="nav-text">==10==. SpringSecurity</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#11-短信"><span class="nav-number"></span> <span class="nav-text">11. 短信</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#12-自行车"><span class="nav-number"></span> <span class="nav-text">12. 自行车</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#13-骑行-解锁"><span class="nav-number"></span> <span class="nav-text">13. 骑行 解锁</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#14-骑行轨迹"><span class="nav-number"></span> <span class="nav-text">14. 骑行轨迹</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#15-Swagger"><span class="nav-number"></span> <span class="nav-text">15. Swagger</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#16-终章"><span class="nav-number"></span> <span class="nav-text">16. 终章</span></a><ol class="nav-child"><li class="nav-item nav-level-6"><a class="nav-link" href="#xx共享单车："><span class="nav-number">1.</span> <span class="nav-text">xx共享单车：</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2018</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">S-Name Small·kk</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Pisces</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  

  

  
  

  

  

  

</body>
</html>
